#include "server_in6.h"

#define LOGU printf

const size_t each_size = 1000;
const size_t pack_size = 1000+64;

const char conn_tag = '<';
const char conn_cfm_tag = ',';
const char disc_tag = '>';
const char disc_cfm_tag = '.';
const char pack_tag = '+';
const char pack_cfm_tag = '-';

static u32 atou(const char *str)
{
    u32 ultimate = UINT_MAX;
    u32 quotient = UINT_MAX/10;
    u32 residuum = UINT_MAX%10;
    u32 sum = 0;

    while (isspace(*str))
        str++;

    while (isdigit(*str))
    {
        u32 value = *str - '0';

        if (sum > quotient)
            return ultimate;

        if (sum == quotient && value >= residuum)
            return ultimate;

        sum = sum*10 + value;
        str++;
    }

    return sum;
}


//  < %10d\n
//  < %10u %10u\n
//  > %10d\n
//  + %10d %10d\n%.*s
//  - %10d %10d\n

namespace reldt {

Server_in6::Server_in6()
{
#ifdef WIN32
    WSADATA data;
	WSAStartup(MAKEWORD(2,2), &data);
#endif

    state = STATE_IDLE;
    sock = -1;
    sessid = 0;
    send_tid = 0;
    recv_tid = 0;
}

Server_in6::~Server_in6()
{
    clear();
}

//reborn
void Server_in6::clear()
{
    LOGU("clear server resource\n");

    if (state != STATE_IDLE)
    {
        sessid = 0;
        send_tid = 0;
        recv_tid = 0;
        send_list.clear();
        recv_list.clear();

        if (sock != -1)
        {
            close(sock);
            sock = -1;
        }

        state = STATE_IDLE;
    }
}

int Server_in6::accept(const char *src_ip6, u16 src_port, int timeout)
{
    if (state != STATE_IDLE)
    {
        LOGU("server state not idle, return err\n");
        return -1;
    }

    state = STATE_ACCEPTING;

	sock = ::socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
	if (sock == -1)
	{
        LOGU("fail to alloc socket, return err\n");
		return -11;
	}

    int opt = 1;
    ::setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));

    struct in6_addr in;
    inet_pton(AF_INET6, src_ip6, (void *)&in);

    self.sin6_family = AF_INET6;
	self.sin6_port = htons(src_port);
	self.sin6_addr = in;

	if (::bind(sock, (struct sockaddr *)&self, sizeof(self)))
	{
        LOGU("fail to bind socket, return err\n");
		::close(sock);
        sock = -1;
		return -12;
	}

    while (true)
    {
    	fd_set fds;
    	FD_ZERO(&fds);
    	FD_SET(sock, &fds);

        timeval tv;
    	tv.tv_sec = 1;
    	tv.tv_usec = 0;

        int rc = ::select(sock+1, &fds, NULL, NULL, &tv);

        //timeout, retry
        if (rc <= 0)
    	{
    		continue;
    	}

        char str[pack_size];

    	rc = recv_str(str, sizeof(str), true);
    	if (rc <= 0)
    	{
    		continue;
    	}

        if (str[0] != conn_tag || rc != 13)
        {
            LOGU("conn format error, return err\n");
            continue;
        }

        sessid = atou(str+2);
        if (sessid == 0)
        {
            LOGU("conn sessid illegal, return err\n");
            continue;
        }

        send_conn_cfm(sessid);

        state = STATE_CONNECTED;
        thread.start(entry, this);
        break;
    }

    return 0;
}

int Server_in6::disconn()
{
    if (state != STATE_CONNECTED)
    {
        LOGU("server state not connected, return err\n");
        return -1;
    }

    state = STATE_DISCONNING;
    thread.join();

    int disc_times = 0;

    struct timeval tv;
	tv.tv_sec = 1;
	tv.tv_usec = 0;

    while (true)
    {
        send_disc(sessid);
        disc_times++;

    	fd_set fds;
    	FD_ZERO(&fds);
    	FD_SET(sock, &fds);

        int rc = select(sock+1, &fds, NULL, NULL, &tv);

        //timeout, retry
        if (rc <= 0)
        {
            if (disc_times > 2)
            {
                LOGU("wait disc cfm timeout, return err\n");
                clear();
                return -2;
            }

        	tv.tv_sec = 1;
        	tv.tv_usec = 0;

            send_disc(sessid);
            disc_times++;
            continue;
        }

        char str[pack_size];

    	rc = recv_str(str, sizeof(str), false);
    	if (rc <= 0)
    	{
    		continue;
    	}

        if (str[0] != disc_cfm_tag || rc != 13)
        {
            LOGU("disc cfm format error, return err\n");
            continue;
        }

        if (atou(str+2) != sessid)
        {
            LOGU("disc cfm sessid error, return err\n");
            continue;
        }

        clear();
        break;
    }

	return 0;
}

int Server_in6::send(const char *msg, int msglen)
{
    if (state != STATE_CONNECTED)
    {
        LOGU("send when not connect, return err\n");
        return -30;
    }

    mutex.lock();

    string tmp(msg, msglen);
    send_list.push_back(tmp);

    mutex.unlock();
    return 0;
}

int Server_in6::send_str(const char *str, int len)
{
    LOGU("send msg: ================\n");
    LOGU("%.*s\n", len, str);

	if (::sendto(sock, str, len, 0, (struct sockaddr *)&peer, sizeof(peer)) <= 0)
	{
        LOGU("sendto return error\n");
		return -51;
	}
    return 0;
}

int Server_in6::recv_str(char *str, int len, bool accept)
{
    struct sockaddr_in6 from;
    socklen_t fromlen = sizeof(from);

    int rc = ::recvfrom(sock, str, len, 0, (struct sockaddr *)&from, &fromlen);
	if (rc <= 0)
	{
        LOGU("recvfrom return error\n");
        return -53;
	}

    if (accept)
    {
        peer = from;
    }

    if (memcmp(&from.sin6_addr, &peer.sin6_addr, sizeof(struct in6_addr)))
    {
        LOGU("peer addr is illegal\n");
        return -55;
    }

    LOGU("recv msg: ================\n");
    LOGU("%.*s\n", rc, str);
    return rc;
}

int Server_in6::send_conn_cfm(u32 sessid)
{
    char str[pack_size];
    int len = sprintf(str, ", %10u\n", sessid);
	return send_str(str, len);
}

int Server_in6::send_disc(u32 sessid)
{
    char str[pack_size];
    int len = sprintf(str, "> %10u\n", sessid);
	return send_str(str, len);
}

int Server_in6::send_disc_cfm(u32 sessid)
{
    char str[pack_size];
    int len = sprintf(str, ". %10u\n", sessid);
	return send_str(str, len);
}

int Server_in6::send_pack(u32 sessid, u32 tid, u32 seq, const string &pack)
{
    char str[pack_size];
    int len = sprintf(str, "+ %10u %10u %10u\n%.*s", sessid, tid, seq, pack.size(), pack.c_str());
	return send_str(str, len);
}

int Server_in6::send_pack_cfm(u32 sessid, u32 tid, u32 seq)
{
    char str[pack_size];
    int len = sprintf(str, "- %10u %10u %10u\n", sessid, tid, seq);
	return send_str(str, len);
}

void Server_in6::entry(void * arg)
{
    Server_in6 *server = (Server_in6 *)arg;
    server->process();
}

int Server_in6::process()
{
	typedef std::map<int, string> Map;
    typedef Map::iterator Cur;

    Map recv_map;
    Map send_map;

    struct timeval tv;
	tv.tv_sec = 1;
	tv.tv_usec = 0;

    int window = 10; //window of sending flow
    int counter = 0; //cfm received per second
	Cur send_cur;    //cursor to sending pack

    while (state == STATE_CONNECTED)
    {
        mutex.lock();

        //send new message
		if (send_map.size() == 0 && send_list.size() > 0)
        {
            send_tid = (u32)time(NULL);
            counter = 0;

            string tmp = send_list.front();
            send_list.pop_front();

            const char *msg = tmp.c_str();
            u32 msglen = tmp.size();
            string sub;

            for (u32 i=0; i<msglen/each_size; i++)
            {
                sub.assign(msg+i*each_size, each_size);
				send_map.insert(std::make_pair(i, sub));
            }

            sub.assign(msg+msglen-msglen%each_size, msglen%each_size);
			send_map.insert(std::make_pair(msglen/each_size, sub));

			send_cur=send_map.begin();

            for (int i=0; i<window && send_cur!=send_map.end(); i++, send_cur++)
            {
                const int seq = send_cur->first;
                const string &ref = send_cur->second;
                send_pack(sessid, send_tid, seq, ref);
            }
        }

        mutex.unlock();

        //check read event
    	fd_set fds;
    	FD_ZERO(&fds);
    	FD_SET(sock, &fds);

        int rc = select(sock+1, &fds, NULL, NULL, &tv);

        //timeout
        if (rc <= 0)
        {
            //nothing to be sent
            if (send_map.size() == 0)
            {
                continue;
            }

            if (counter >= window)
            {
                //enlarge window size
                for (int i=0; i<counter-window && send_cur!=send_map.end(); i++, send_cur++)
                {
                    const int seq = send_cur->first;
                    const string &ref = send_cur->second;
                    send_pack(sessid, send_tid, seq, ref);
                }
                window = counter;
            }
            else
            {
                //retrans first N not confirmed
                Cur cur = send_map.begin();
                for (int i=0; i<window-counter && cur!=send_map.end(); i++, cur++)
                {
                    const int seq = cur->first;
                    const string &ref = cur->second;
                    send_pack(sessid, send_tid, seq, ref);
                }
            }

            counter = 0;
        	tv.tv_sec = 1;
        	tv.tv_usec = 0;
            continue;
        }

        //receive str
        char str[pack_size];

    	rc = recv_str(str, sizeof(str), false);
    	if (rc <= 0)
    	{
    		continue;
    	}

        //it's conn
        if (str[0] == conn_tag && rc == 13)
        {
            u32 sid = atou(str+2);
            if (sid != sessid)
            {
                LOGU("conn sessid illegal, continue\n");
                continue;
            }

            send_conn_cfm(sid);
            continue;
        }

        //it's disc
        if (str[0] == disc_tag && rc == 13)
        {
            u32 sid = atou(str+2);
            if (sid != sessid)
            {
                LOGU("disc sessid illegal, continue\n");
                continue;
            }

            send_disc_cfm(sid);

            clear();
            on_disconn();
            return 0;
        }

        //it's pack
        if (str[0] == pack_tag && rc >= 35)
        {
            u32 sid = atou(str+2);
            if (sid != sessid)
            {
                LOGU("pack sessid illegal, continue\n");
                continue;
            }

            u32 tid = atou(str+13);

            //pack of current message
            if (tid == recv_tid)
            {
                u32 seq = atou(str+24);
                send_pack_cfm(sessid, recv_tid, seq);

                //if retran, continue
                if (recv_map.find(seq) != recv_map.end())
                {
                    continue;
                }

                string sub;
                sub.assign(str+35, rc-35);
    			recv_map.insert(std::make_pair(seq, sub));

                //all pack received
                Cur cur = recv_map.find(recv_map.size()-1);
                if (cur != recv_map.end() && cur->second.size() < each_size)
                {
    				string recv_str;
    				for (int i=0; i<(int)recv_map.size(); i++)
    				{
    					cur = recv_map.find(i);
    					if (cur != recv_map.end())
    					{
    						recv_str.append(cur->second);
    					}
    				}
    				on_recv(recv_str.c_str(), recv_str.size());
                }
                continue;
            }

            //pack of new message
            if (tid > recv_tid)
            {
                recv_tid = tid;
                recv_map.clear();

                u32 seq = atou(str+24);
                send_pack_cfm(sessid, recv_tid, seq);

                string sub;
                sub.assign(str+35, rc-35);
    			recv_map.insert(std::make_pair(seq, sub));
                continue;
            }

            continue;
        }

        //recv pack confirm
        if (str[0] == pack_cfm_tag && rc == 35)
        {
            u32 sid = atou(str+2);
            if (sid != sessid)
            {
                LOGU("pack cfm sessid illegal, continue\n");
                continue;
            }

            u32 tid = atou(str+13);
            if (tid == send_tid)
            {
                u32 seq = atou(str+24);
                Cur cur = send_map.find(seq);
                if (cur != send_map.end())
                {
                    send_map.erase(cur);
                    counter++;

                    //all confirm received
                    if (send_map.size() == 0)
                    {
                        on_finish();
                        continue;
                    }

                    //send another pack
                    if (send_cur != send_map.end())
                    {
                        const int seq = send_cur->first;
                        const string &ref = send_cur->second;
                        send_pack(sessid, send_tid, seq, ref);
                        send_cur++;
                    }
                }
            }
        }
    }

    return 0;
}

}
